﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;
using VsxFactory.Modeling.Strategies;
using Microsoft.VisualStudio.Modeling.Diagrams;

namespace VsxFactory.Modeling
{
    public class DSLModelElementEventArgs : EventArgs
    {
        public ModelElement ModelElement { get; set; }
    }

    public class DSLModelPropertyEventArgs : DSLModelElementEventArgs 
    {
        public Guid PropertyId { get; set; }
        public object OldValue { get; set; }
    }

    /// <summary>
    /// Classe permettant de gérer la synchronization à partir du modèle.
    /// </summary>
    public class DSLModelObserver : IDisposable
    {
        public event EventHandler<DSLModelElementEventArgs> ElementAdded;
        public event EventHandler<DSLModelElementEventArgs> ElementRemoved;
        public event EventHandler<DSLModelPropertyEventArgs> PropertyChanged;

        public class SynchronizeCodeDeleteRule : Microsoft.VisualStudio.Modeling.DeletingRule
        {
            public event EventHandler<ElementDeletingEventArgs> OnElementDeleting;

            public override void ElementDeleting(ElementDeletingEventArgs e)
            {
                if (OnElementDeleting != null)
                {
                    //if (e.ModelElement.Store.GetSynchronizingStatus(e.ModelElement) != CodeGenerationEvent.None)
                    //    return;
                    //e.ModelElement.Store.SetSynchronizeTransaction(e.ModelElement, CodeGenerationEvent.OnModelElementRemoved);
                    OnElementDeleting(this, e);
                }
            }
        }

        /// <summary>
        /// Gets or sets the store.
        /// </summary>
        /// <value>The store.</value>
        public Store Store { get; private set; }

        private Guid _domainClassId;

        public DSLModelObserver(Store store)
        {
            this.Store = store;
            _domainClassId = ModelElement.DomainClassId;

            store.EventManagerDirectory.ElementAdded.Add(
                        store.DomainDataDirectory.GetDomainClass(_domainClassId),
                        new EventHandler<ElementAddedEventArgs>(OnElementAdded));
            store.EventManagerDirectory.ElementDeleted.Add(
                        store.DomainDataDirectory.GetDomainClass(_domainClassId),
                        new EventHandler<ElementDeletedEventArgs>(OnElementRemoved));
            store.EventManagerDirectory.ElementPropertyChanged.Add(
                          store.DomainDataDirectory.GetDomainClass(_domainClassId),
                          new EventHandler<ElementPropertyChangedEventArgs>(OnPropertyChanged));

            // Ajout de le régle pour la suppression
            dynamic dc = store.DomainDataDirectory.GetDomainClass(_domainClassId);
            var p = dc.GetType().GetProperty("DeletingRules", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            var rule = new SynchronizeCodeDeleteRule();
            rule.OnElementDeleting += new EventHandler<ElementDeletingEventArgs>(OnElementRemoving);
            rule.FireTime = TimeToFire.Inline;

            object deletingRules = p.GetValue(dc, null);
            deletingRules.GetType().GetMethod("Add").Invoke(deletingRules, new object[] { rule });

            var m = store.RuleManager.GetType().GetMethod("RegisterRule", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            m.Invoke(store.RuleManager, new object[] { rule });
        }

        /// <summary>
        /// Ensures the property change.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.VisualStudio.Modeling.ElementPropertyChangedEventArgs"/> instance containing the event data.</param>
        void OnPropertyChanged(object sender, ElementPropertyChangedEventArgs e)
        {
            //if (!e.ModelElement.Store.InUndoRedoOrRollback)
            //    return;
            if (e.ModelElement is NodeShape || e.ModelElement is PresentationViewsSubject || e.ModelElement is ParentShapeHasRelativeChildShapes)
                return;
            if (PropertyChanged != null)
                PropertyChanged(this, new DSLModelPropertyEventArgs() { ModelElement = e.ModelElement, PropertyId = e.DomainProperty.Id, OldValue = e.OldValue });
        }

        /// <summary>
        /// Ensures the element.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.VisualStudio.Modeling.ElementAddedEventArgs"/> instance containing the event data.</param>
        void OnElementAdded(object sender, ElementAddedEventArgs e)
        {
            if (e.ModelElement is NodeShape || e.ModelElement is PresentationViewsSubject || e.ModelElement is ParentShapeHasRelativeChildShapes)
                return;
            if (ElementAdded != null)
                ElementAdded(this, new DSLModelElementEventArgs() { ModelElement = e.ModelElement });
        }

        void OnElementRemoved(object sender, ElementDeletedEventArgs e)
        {
            if (!e.ModelElement.Store.InUndoRedoOrRollback)
                return;
            if (e.ModelElement is NodeShape || e.ModelElement is PresentationViewsSubject || e.ModelElement is ParentShapeHasRelativeChildShapes)
                return;
            if (ElementRemoved != null)
                ElementRemoved(this, new DSLModelElementEventArgs() { ModelElement = e.ModelElement });
        }

        void OnElementRemoving(object sender, ElementDeletingEventArgs e)
        {
            if (e.ModelElement is NodeShape)
                return;
            if (ElementRemoved != null)
                ElementRemoved(this, new DSLModelElementEventArgs() { ModelElement = e.ModelElement });
        }        

        public virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.Store.EventManagerDirectory.ElementAdded.Remove(
                    this.Store.DomainDataDirectory.GetDomainClass(_domainClassId),
                    new EventHandler<ElementAddedEventArgs>(OnElementAdded));
                this.Store.EventManagerDirectory.ElementDeleted.Remove(
                    this.Store.DomainDataDirectory.GetDomainClass(_domainClassId),
                    new EventHandler<ElementDeletedEventArgs>(OnElementRemoved));
                this.Store.EventManagerDirectory.ElementPropertyChanged.Remove(
                     this.Store.DomainDataDirectory.GetDomainClass(_domainClassId),
                     new EventHandler<ElementPropertyChangedEventArgs>(OnPropertyChanged));
                this.Store = null;
            }
        }

        ~DSLModelObserver()
        {
            Dispose(false);
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
